function padOrCrop( str, length, pad ) {
	if ( str.length > length ) //crop
	{
		return str.substr( 0, length );
	}
	if ( str.length < length ) //pad
	{
		for( var i=str.length; i<length; i++ )
		{
			str+=pad;
		}	
		return str;		
	}
	return str;
}

function isSet(x) {
    if (typeof x == 'undefined') return false;
    return true;
}

function runIfSet(f) {
    if (isSet(f)) f();
}

var TestRunner = {
    globalContext: this,
    _getFixtures: function () {
        var fixtures = [];

        for (var o in this.globalContext) {
            if (o.match(/Fixture$/)) {
                if (typeof this.globalContext[o] == "object") {
                    fixtures[o] = this.globalContext[o];
                }
                if (typeof this.globalContext[o] == "function") {
                    fixtures[o] = new this.globalContext[o];
                }
            }
        }

        return fixtures;
    },
    run: function (options) {
        var success = true;
        var fixtures = this._getFixtures();

        print("-------------------------------------------------------------------------------\n");
        for (var f in fixtures) {
            success &= this.runFixture(fixtures[f], f);
        }

        //TODO: results for global (all fixtures)

        return success;
    },
    runFixture: function (testFixture, testFixtureName) {

        print("Testing fixture '" + testFixtureName + "'.\n");

        var run = 0;
        this.failures = [];

        var setUp = testFixture["SetUp"];
        var tearDown = testFixture["TearDown"];
        var testFixtureSetUp = testFixture["TestFixtureSetUp"];
        var testFixtureTearDown = testFixture["TestFixtureTearDown"];

        var startTime = new Date();
        try {
            runIfSet(testFixtureSetUp);
            for (f in testFixture) {
                if ((typeof testFixture[f] == "function") &&
				    (f != "SetUp") && (f != "TearDown") && (f != "TestFixtureSetUp") && (f != "TestFixtureTearDown")) {
                    runIfSet(setUp);
                    this.runTest(testFixture[f], f);
                    runIfSet(tearDown);
                    run++;
                }
            }
            runIfSet(testFixtureTearDown);
        }
        catch (e) {
            print(e.message);
            print(e.stack);
        }
        var endTime = new Date();

        print("\n");

        if (this.failures.length > 0) {
            print("-- Failures --\n");
            for (var i = 0; i < this.failures.length; i++) {
                print("Test case : ", this.failures[i].testName, "\n");
                print(this.failures[i].error.message, "\n");
                if (typeof this.failures[i].error.message == 'undefined') {
                    print(this.failures[i].error.stack, "\n");
                }
            }
            print("--------------\n");
        }

        print("Tests run: ", run, ", Errors: ", this.failures.length, " Time: ", (endTime.getTime() - startTime.getTime()) / 1000, " seconds \n");
        print("-------------------------------------------------------------------------------\n");

        return this.failures.length == 0;
    },
    runTest: function (test, testName) {
        try {
            test();

            print(".");
        }
        catch (e) {
            print("F");
            this.failures[this.failures.length] = { testName: testName, error: e };
            return false;
        }
        return true;
    }
};
